package com.small.sentinel.slots.block.degrade.circuitbreaker;

import com.small.sentinel.Context;
import com.small.sentinel.Entry;
import com.small.sentinel.slots.block.degrade.DegradeRule;
import com.small.sentinel.slots.statistic.base.LeapArray;
import com.small.sentinel.slots.statistic.base.WindowWrap;
import com.small.sentinel.util.TimeUtil;

import java.util.List;
import java.util.concurrent.atomic.LongAdder;

public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker {

    //慢请求比率最大值
    private static final double SLOW_REQUEST_RATIO_MAX_VALUE = 1.0d;
    //最大允许响应时间
    private final long maxAllowedRt;
    //最大慢请求比率
    private final double maxSlowRequestRatio;
    //最小请求数量
    private final int minRequestAmount;
    //窗口数据统计
    private final LeapArray<SlowRequestCounter> slidingCounter;

    public ResponseTimeCircuitBreaker(DegradeRule rule) {
        this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs()));
    }

    ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray<SlowRequestCounter> stat) {
        super(rule);
        this.maxAllowedRt = Math.round(rule.getCount());
        this.maxSlowRequestRatio = rule.getSlowRatioThreshold();
        this.minRequestAmount = rule.getMinRequestAmount();
        this.slidingCounter = stat;
    }

    @Override
    public void onRequestComplete(Context context) {
        Entry curEntry = context.getCurEntry();
        SlowRequestCounter counter = slidingCounter.currentWindow().value();
        long completeTimestamp = curEntry.getCompleteTimestamp();
        if (completeTimestamp <= 0){
            completeTimestamp = TimeUtil.currentTimeMillis();
        }

        long rt = completeTimestamp - curEntry.getCreateTimestamp();

        if (rt >maxAllowedRt){
            counter.slowCount.increment();
        }
        counter.totalCount.increment();
        
        handleStateChangeWhenThresholdExceeded(rt);
    }

    /**
     * 超过阈值时处理状态更改
     * @param rt 平均时长
     */
    private void handleStateChangeWhenThresholdExceeded(long rt) {

        slidingCounter.currentWindow().value();

        if (currentState.get() == State.OPEN){
            return;
        }
        if (currentState.get() == State.HALF_OPEN) {
            if (rt > maxAllowedRt) {
                fromHalfOpenToOpen();
            } else {
                fromHalfOpenToClose();
            }
            return;
        }
        List<SlowRequestCounter> counters = slidingCounter.values();
        long slowCount = 0;
        long totalCount = 0;
        for (SlowRequestCounter counter : counters) {
            slowCount += counter.slowCount.sum();
            totalCount += counter.totalCount.sum();
        }
        if (totalCount < minRequestAmount) {
            return;
        }
        // 慢调用比率 = 慢调用次数 / 总次数
        double currentRatio = slowCount * 1.0d / totalCount;
        if (currentRatio > maxSlowRequestRatio) {
            transformToOpen();
        }
        // 特例，当前慢调用比率=配置慢调用比率=1
        if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 &&
                Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) {
            transformToOpen();
        }
    }

    @Override
    void resetStat() {
        slidingCounter.currentWindow().value().reset();
    }

    static class SlowRequestLeapArray extends LeapArray<SlowRequestCounter> {

        public SlowRequestLeapArray(int sampleCount, int intervalInMs) {
            super(sampleCount, intervalInMs);
        }

        @Override
        public SlowRequestCounter newEmptyBucket(long timeMillis) {
            return new SlowRequestCounter();
        }

        @Override
        protected WindowWrap<SlowRequestCounter> resetWindowTo(WindowWrap<SlowRequestCounter> w, long startTime) {
            w.resetTo(startTime);
            w.value().reset();
            return w;
        }
    }

    static class SlowRequestCounter {
        private LongAdder slowCount;
        private LongAdder totalCount;

        public SlowRequestCounter() {
            this.slowCount = new LongAdder();
            this.totalCount = new LongAdder();
        }

        public LongAdder getSlowCount() {
            return slowCount;
        }

        public LongAdder getTotalCount() {
            return totalCount;
        }

        public SlowRequestCounter reset() {
            slowCount.reset();
            totalCount.reset();
            return this;
        }

        @Override
        public String toString() {
            return "SlowRequestCounter{" +
                    "slowCount=" + slowCount +
                    ", totalCount=" + totalCount +
                    '}';
        }
    }
}
